package multithread.framework.utility;

import static multithread.framework.constant.XMLConfigConstant.AFTER_ELEMENT;
import static multithread.framework.constant.XMLConfigConstant.ALLAFTER_ELEMENT;
import static multithread.framework.constant.XMLConfigConstant.ALLBEFORE_ELEMENT;
import static multithread.framework.constant.XMLConfigConstant.BEFORE_ELEMENT;
import static multithread.framework.constant.XMLConfigConstant.GROUPAFTER_ELEMENT;
import static multithread.framework.constant.XMLConfigConstant.GROUPBEFORE_ELEMENT;
import static multithread.framework.constant.XMLConfigConstant.THREAD_AFTER_TYPE;
import static multithread.framework.constant.XMLConfigConstant.THREAD_ALL_AFTER_TYPE;
import static multithread.framework.constant.XMLConfigConstant.THREAD_ALL_BEFORE_TYPE;
import static multithread.framework.constant.XMLConfigConstant.THREAD_BEFORE_TYPE;
import static multithread.framework.constant.XMLConfigConstant.THREAD_GROUP_AFTER_TYPE;
import static multithread.framework.constant.XMLConfigConstant.THREAD_GROUP_BEFORE_TYPE;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import multithread.framework.annotation.ThreadAfter;
import multithread.framework.annotation.ThreadAllAfter;
import multithread.framework.annotation.ThreadAllBefore;
import multithread.framework.annotation.ThreadBefore;
import multithread.framework.annotation.ThreadFor;
import multithread.framework.annotation.ThreadGroupAfter;
import multithread.framework.annotation.ThreadGroupBefore;

public final class Utilities {
	private Utilities() {
	}

	// -----------------------------------------------------------------------------------------------------------------annotation
	// utilities

	public final static List<Class<? extends Annotation>> COMMONANNOTATIONS;
	static {
		List<Class<? extends Annotation>> temp = new ArrayList<Class<? extends Annotation>>();
		temp.add(ThreadAllBefore.class);
		temp.add(ThreadAllAfter.class);
		temp.add(ThreadGroupBefore.class);
		temp.add(ThreadGroupAfter.class);
		temp.add(ThreadBefore.class);
		temp.add(ThreadAfter.class);
		COMMONANNOTATIONS = Collections.unmodifiableList(temp);
	}

	public static final Map<Class<? extends Annotation>, String> ANNOTATION_ELEMENT_NAMES = new HashMap<Class<? extends Annotation>, String>();
	static {
		ANNOTATION_ELEMENT_NAMES.put(ThreadAllBefore.class, ALLBEFORE_ELEMENT);
		ANNOTATION_ELEMENT_NAMES.put(ThreadAllAfter.class, ALLAFTER_ELEMENT);
		ANNOTATION_ELEMENT_NAMES.put(ThreadGroupBefore.class,
				GROUPBEFORE_ELEMENT);
		ANNOTATION_ELEMENT_NAMES
				.put(ThreadGroupAfter.class, GROUPAFTER_ELEMENT);
		ANNOTATION_ELEMENT_NAMES.put(ThreadBefore.class, BEFORE_ELEMENT);
		ANNOTATION_ELEMENT_NAMES.put(ThreadAfter.class, AFTER_ELEMENT);
	}

	public static final Map<Class<? extends Annotation>, Integer> ANNOTATION_TYPES = new HashMap<Class<? extends Annotation>, Integer>();
	static {
		ANNOTATION_TYPES.put(ThreadAllBefore.class, THREAD_ALL_BEFORE_TYPE);
		ANNOTATION_TYPES.put(ThreadAllAfter.class, THREAD_ALL_AFTER_TYPE);
		ANNOTATION_TYPES.put(ThreadGroupBefore.class, THREAD_GROUP_BEFORE_TYPE);
		ANNOTATION_TYPES.put(ThreadGroupAfter.class, THREAD_GROUP_AFTER_TYPE);
		ANNOTATION_TYPES.put(ThreadBefore.class, THREAD_BEFORE_TYPE);
		ANNOTATION_TYPES.put(ThreadAfter.class, THREAD_AFTER_TYPE);
	}

	public static boolean containNeedRunMultiThreadAnnotation(
			Class<? extends Annotation>... annotationClzs) {
		if (annotationClzs != null) {
			for (Class<? extends Annotation> clz : annotationClzs) {
				if (isNeedRunMultiThreadAnnotation(clz))
					return true;
			}
		}
		return false;
	}

	public static boolean containNotNeedRunMultiThreadAnnotation(
			Class<? extends Annotation>... annotationClzs) {
		if (annotationClzs != null) {
			for (Class<? extends Annotation> clz : annotationClzs) {
				if (!isNeedRunMultiThreadAnnotation(clz))
					return true;
			}
		}
		return false;
	}

	public static boolean isNeedRunMultiThreadAnnotation(
			Class<? extends Annotation> annotation) {
		if (annotation == ThreadFor.class) {
			return true;
		}
		return false;
	}

	/**
	 * return the element name of the XML config file based on provided
	 * annotation class
	 * <p>
	 * eg. allbefore, allafter...
	 * 
	 * @param clazz
	 * @return Element name
	 */
	public static String getAnnotationElementName(Class<?> clazz) {
		return ANNOTATION_ELEMENT_NAMES.get(clazz);
	}

	/**
	 * return a int value as annotation type. eg. 1(ThreadGroupFor),
	 * 2(ThreadBefore)
	 * 
	 * @param clazz
	 * @return Annotation type
	 */
	public static int getAnnotationType(Class<?> clazz) {
		return ANNOTATION_TYPES.get(clazz).intValue();
	}

	// --------------------------------------------------------------------------------------------------------type
	// convert
	public final static Map<Class<?>, Class<?>> ARRAYTOELEMENTTYPE;
	static {
		Map<Class<?>, Class<?>> temp = new HashMap<Class<?>, Class<?>>();
		temp.put(int[].class, int.class);
		temp.put(byte[].class, byte.class);
		temp.put(short[].class, short.class);
		temp.put(long[].class, long.class);
		temp.put(float[].class, float.class);
		temp.put(double[].class, double.class);
		temp.put(char[].class, char.class);
		temp.put(boolean[].class, boolean.class);
		temp.put(String[].class, String.class);
		ARRAYTOELEMENTTYPE = Collections.unmodifiableMap(temp);
	}

	/**
	 * convert string value to the corresponding primitive
	 * 
	 * @param primitiveType
	 *            Class<?>
	 * @param value
	 *            String
	 * @return Object
	 */
	public static Object convertToPrimitive(Class<?> primitiveType, String value) {
		if (primitiveType == int.class) {
			return Integer.valueOf(value).intValue();
		} else if (primitiveType == long.class) {
			return Long.valueOf(value).longValue();
		} else if (primitiveType == float.class) {
			return Float.valueOf(value).floatValue();
		} else if (primitiveType == double.class) {
			return Double.valueOf(value).doubleValue();
		} else if (primitiveType == boolean.class) {
			return Boolean.valueOf(value).booleanValue();
		} else if (primitiveType == byte.class) {
			return Byte.valueOf(value).byteValue();
		} else if (primitiveType == short.class) {
			return Short.valueOf(value).shortValue();
		} else if (primitiveType == char.class) {
			return value.charAt(0);
		} else {
			throw new IllegalArgumentException(primitiveType
					+ " is not primitive type.");
		}
	}

	/**
	 * Convert string array value to the corresponding type array. Only support
	 * primitive type array and string array self
	 * 
	 * @param arrayTypeClass
	 *            Class<?> the array class type
	 * @param values
	 *            String[]
	 * @return Object array by convert from string array
	 */
	public static Object convertToArray(Class<?> arrayTypeClass, String[] values) {
		Class<?> elementTypeClass = Utilities.ARRAYTOELEMENTTYPE
				.get(arrayTypeClass);
		if (!elementTypeClass.isPrimitive())
			return values;
		Object array = Array.newInstance(elementTypeClass, values.length);
		for (int i = 0; i < values.length; i++) {
			Array.set(array, i, convertToPrimitive(elementTypeClass, values[i]));
		}
		return array;
	}

	// -----------------------------------------------------------------------------------------------------------------load
	// external class

	/**
	 * cache for loaded class
	 */
	private final static Map<Long, Class<?>> LOADEDCLASSMAP = new HashMap<Long, Class<?>>();

	/**
	 * load external class file
	 * 
	 * @param classFile
	 *            File
	 * @return Class<?>
	 */
	public static Class<?> findClass(File classFile) {
		ClassLoader classLoader = ClassLoader.getSystemClassLoader();
		try {
			Method method = ClassLoader.class.getDeclaredMethod("defineClass",
					new Class[] { String.class, byte[].class, int.class,
							int.class });
			method.setAccessible(true);
			byte[] totalData = toByteArray(classFile);
			Long key = getKey(totalData);
			Class<?> claz = LOADEDCLASSMAP.get(key);
			if (claz == null) {
				claz = (Class<?>) method.invoke(classLoader, new Object[] {
						null, totalData, 0, totalData.length });
				LOADEDCLASSMAP.put(key, claz);
			}
			return claz;
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	private static Long getKey(byte[] bytes) {
		long sum = 0;
		for (int i = 0; i < bytes.length; i++) {
			sum = 31 * i + bytes[i];
		}
		return sum;
	}

	/**
	 * convert class file to byte array
	 * 
	 * @param classFile
	 *            File
	 * @return
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static byte[] toByteArray(File classFile)
			throws FileNotFoundException, IOException {
		byte[] totalData = new byte[1024];
		int total = 0;
		int current = 0;
		byte[] buff = new byte[1024];
		InputStream in = null;
		try {
			in = new FileInputStream(classFile);
			do {
				current = in.read(buff);
				if (current == -1)
					break;
				byte[] temp = new byte[total + current];
				if (total >= totalData.length) {
					System.arraycopy(totalData, 0, temp, 0, total);
				}
				System.arraycopy(buff, 0, temp, total, current);
				totalData = temp;
				total += current;
			} while (true);
		} finally {
			if (in != null)
				in.close();
		}
		return totalData;
	}

	// -----------------------------------------------------------------------------------------------------------------fragment
	private static final DateFormat FORMATER = new SimpleDateFormat(
			"yyyy-MM-dd HH:mm:ss");

	public static String now() {
		return FORMATER.format(new Date());
	}

	@SuppressWarnings("unchecked")
	public static <T> List<T> requireNotNull(List<T> list) {
		return (List<T>) (list == null ? Collections.emptyList() : list);
	}

	public static Object newInstance(Class<?> clz) {
		if (clz == null)
			return null;
		try {
			Object testcase = clz.newInstance();
			return testcase;
		} catch (InstantiationException e) {
			e.printStackTrace();
			return new RuntimeException(e);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			return new RuntimeException(e);
		}
	}

	/**
	 * convert array to String, join with ','
	 * 
	 * @param <T>
	 * @param array
	 * @return
	 */
	public static <T> String toArrayString(T... array) {
		StringBuffer buffer = new StringBuffer();
		if (array != null) {
			for (T t : array) {
				buffer.append(String.valueOf(t));
				buffer.append(",");
			}
			if (buffer.length() > 0)
				return buffer.substring(0, buffer.length() - 1);
		}
		return buffer.toString();
	}

}
